package cn.caplike.demo.spring.security.csrf.configuration.security;

import cn.caplike.data.redis.service.spring.boot.starter.RedisService;
import cn.caplike.demo.spring.security.csrf.filter.HttpServletRequestWrapFilter;
import cn.caplike.demo.spring.security.csrf.filter.SimpleAuthenticationFilter;
import cn.caplike.demo.spring.security.csrf.filter.SimpleAuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;

/**
 * Spring security config
 *
 * @author LiKe
 * @version 1.0.0
 * @date 2020-04-26 12:55
 */
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private RedisService redisService;

    private PasswordEncoder passwordEncoder;

    private CsrfTokenRedisRepository csrfTokenRedisRepository;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("caplike").password(passwordEncoder.encode("caplike")).authorities("ADMIN")
        ;
    }

    /**
     * <b>关于 Spring Security 的 CSRF</b>
     * <ul>
     *  <li>
     *      如果 GET 请求,
     *      意味着它只是访问服务器的资源, 没有更新. 所以对于这类请求, Spring Security 的防御策略是允许的;
     *  </li>
     *  <li>
     *      如果是 POST 请求,
     *      因为这类请求是带有更新服务器资源的危险操作.
     *      如果第三方通过劫持令牌来更新服务器资源, 那会造成服务器数据被非法的篡改, 所以这类请求是会被 SpringSecurity CSRF 防御策略拦截的.
     *      在默认的情况下, SpringSecurity 是启用 CSRF 拦截功能的.
     *      前端发起的 POST 请求后端无法正常处理, 保证了安全性, 但影响了正常的使用.
     *      如果关闭 CSRF 防护功能, 虽然可以正常处理 POST 请求, 但是无法防范非法的 POST 请求. 为了辨别合法的 POST 请求, 采用了 TOKEN 的机制.
     *  </li>
     * </ul>
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().ignoringAntMatchers("/login").csrfTokenRepository(csrfTokenRedisRepository)
                .and()
                .authorizeRequests()
                .anyRequest().hasAuthority("ADMIN")
                .and()
                .formLogin().disable()
                .addFilterBefore(new HttpServletRequestWrapFilter(), CsrfFilter.class)
                .addFilterAt(new SimpleAuthenticationFilter(authenticationManager(), redisService), UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(new SimpleAuthorizationFilter(), SimpleAuthenticationFilter.class)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        ;
    }

    // ~ Autowired
    // -----------------------------------------------------------------------------------------------------------------

    @Autowired
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Autowired
    public void setCsrfTokenRedisRepository(CsrfTokenRedisRepository csrfTokenRedisRepository) {
        this.csrfTokenRedisRepository = csrfTokenRedisRepository;
    }

    @Autowired
    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }

    // ~ Bean
    // -----------------------------------------------------------------------------------------------------------------

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
